///PHILOTES Source Code.  (C)2012 PhiloLabs
#pragma once

#ifndef __InstanceBatch_H__
#define __InstanceBatch_H__

#include "OgrePrerequisites.h"
#include "OgreRenderOperation.h"
#include "OgreRenderable.h"
#include "OgreMovableObject.h"
#include "OgreMesh.h"

namespace Ogre
{
	
	

	
	class _OgreExport InstanceBatch : public Renderable, public MovableObject
	{
	public:
		typedef vector<InstancedEntity*>::type InstancedEntityVec;
	protected:
		RenderOperation		mRenderOperation;
		size_t				mInstancesPerBatch;

		InstanceManager		*mCreator;

		MaterialPtr			mMaterial;

		MeshPtr				 mMeshReference;
		Mesh::IndexMap const *mIndexToBoneMap;

		//InstancedEntities are all allocated at build time and kept as "unused"
		//when they're requested, they're removed from there when requested,
		//and put back again when they're no longer needed
		//Note each InstancedEntity has a unique ID ranging from [0; mInstancesPerBatch)
		InstancedEntityVec	mInstancedEntities;
		InstancedEntityVec	mUnusedEntities;

		//This bbox contains all (visible) instanced entities
		AxisAlignedBox		mFullBoundingBox;
		Real				mBoundingRadius;
		bool				mBoundsDirty;
		bool				mBoundsUpdated; //Set to false by derived classes that need it
		Camera				*mCurrentCamera;

		unsigned short		mMaterialLodIndex;

		bool				mDirtyAnimation; //Set to false at start of each _updateRenderQueue

		//False if a technique doesn't support skeletal animation
		bool				mTechnSupportsSkeletal;

		/// Cached distance to last camera for getSquaredViewDepth
		mutable Real mCachedCameraDist;
		/// The camera for which the cached distance is valid
		mutable const Camera *mCachedCamera;

		/// Tells that the list of entity instances with shared transforms has changed
		bool mTransformSharingDirty;

		//When true remove the memory of the VertexData we've created because no one else will
		bool mRemoveOwnVertexData;
		//When true remove the memory of the IndexData we've created because no one else will
		bool mRemoveOwnIndexData;

		virtual void setupVertices( const SubMesh* baseSubMesh ) = 0;
		virtual void setupIndices( const SubMesh* baseSubMesh ) = 0;
		virtual void createAllInstancedEntities(void);
		virtual void deleteAllInstancedEntities(void);
		virtual void deleteUnusedInstancedEntities(void);
		/// Creates a new InstancedEntity instance
		virtual InstancedEntity* generateInstancedEntity(size_t num);

		
		void makeMatrixCameraRelative3x4( float *mat3x4, size_t numFloats );

		//Returns false on errors that would prevent building this batch from the given submesh
		virtual bool checkSubMeshCompatibility( const SubMesh* baseSubMesh );

		void updateVisibility(void);

		
		void defragmentBatchNoCull( InstancedEntityVec &usedEntities );

		
		void defragmentBatchDoCull( InstancedEntityVec &usedEntities );

	public:
		InstanceBatch( InstanceManager *creator, MeshPtr &meshReference, const MaterialPtr &material,
						size_t instancesPerBatch, const Mesh::IndexMap *indexToBoneMap,
						const String &batchName );
		virtual ~InstanceBatch();

		MeshPtr& _getMeshRef() { return mMeshReference; }

		
		void _setInstancesPerBatch( size_t instancesPerBatch );

		const Mesh::IndexMap* _getIndexToBoneMap() const { return mIndexToBoneMap; }

		
		bool _supportsSkeletalAnimation() const { return mTechnSupportsSkeletal; }

		
		void _updateBounds(void);

		
		virtual size_t calculateMaxNumInstances( const SubMesh *baseSubMesh, uint16 flags ) const = 0;

		
		virtual RenderOperation build( const SubMesh* baseSubMesh );

		
		virtual void buildFrom( const SubMesh *baseSubMesh, const RenderOperation &renderOperation );

		const Ogre::MeshPtr& _getMeshReference(void) const { return mMeshReference; }

		
		bool isBatchFull(void) const { return mUnusedEntities.empty(); }

		
		bool isBatchUnused(void) const { return mUnusedEntities.size() == mInstancedEntities.size(); }

		
		void getInstancedEntitiesInUse( InstancedEntityVec &outEntities );

		
		void _defragmentBatch( bool optimizeCulling, InstancedEntityVec &usedEntities );

		
		void _defragmentBatchDiscard(void);

		
		virtual void _boundsDirty(void);

		
		virtual void setStaticAndUpdate( bool bStatic )		{}

		
		virtual bool isStatic() const						{ return false; }

		
		InstancedEntity* createInstancedEntity();

		
		void removeInstancedEntity( InstancedEntity *instancedEntity );

		
		virtual bool useBoneWorldMatrices() const { return true; }

		
		void _markTransformSharingDirty() { mTransformSharingDirty = true; }

		//Renderable overloads
		const MaterialPtr& getMaterial(void) const		{ return mMaterial; }
		void getRenderOperation( RenderOperation& op )	{ op = mRenderOperation; }

		Real getSquaredViewDepth( const Camera* cam ) const;
        const LightList& getLights( void ) const;
		Technique* getTechnique(void) const;

		//MovableObject overloads
		const String& getMovableType(void) const;
		void _notifyCurrentCamera( Camera* cam );
		const AxisAlignedBox& getBoundingBox(void) const;
		Real getBoundingRadius(void) const;

		virtual void _updateRenderQueue(RenderQueue* queue);
		void visitRenderables( Renderable::Visitor* visitor, bool debugRenderables = false );
	};
}

#endif
